package com.kason.spring.context;

import com.kason.spring.BeanDefinition.KasonBeanDefinition;
import com.kason.spring.annotation.Autowired;
import com.kason.spring.annotation.Component;
import com.kason.spring.annotation.ComponentScan;
import com.kason.spring.annotation.Scope;
import com.kason.spring.enuminfo.ScopeEnum;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author kason
 * @date 2020/10/9 14:02
 */
public class KasonApplicationContext {

    private ConcurrentHashMap<String,KasonBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();

    public KasonApplicationContext(Class configClass) throws Exception {
        doScan(configClass);
        initlizeBean();
    }

    private void initlizeBean() throws Exception {
        Iterator<String> iterator = beanDefinitionMap.keySet().iterator();
        while (iterator.hasNext()) {
            String beanName = iterator.next();
            KasonBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            doCreateBean(beanName, beanDefinition);
        }


    }

    private Object doCreateBean(String beanName, KasonBeanDefinition beanDefinition) throws Exception {
        Object o = beanDefinition.getClazz().getDeclaredConstructor().newInstance();
        if (beanDefinition.getScope().equals(ScopeEnum.singleton) && !singletonObjects.containsKey(beanName)) {
            // 这里有可能填充属性
            Field[] declaredFields = o.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    // byType , byName
                    String fieldName = field.getName();
                    Object bean = getBean(fieldName);
                    field.setAccessible(true);
                    field.set(o,bean);
                }
            }
            singletonObjects.put(beanName,o);
        }
        return o ;
    }

    private void doScan(Class configClass) throws ClassNotFoundException {
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScan.value().replace(".","/");
            List<Class<?>> beanClasses = getBeanClass(path);
            doBeanDefinition(beanClasses);
        }
    }

    private void doBeanDefinition(List<Class<?>> beanClasses) {
        for (Class<?> beanClass : beanClasses) {
            if (beanClass.isAnnotationPresent(Component.class)) {
                KasonBeanDefinition beanDefinition = new KasonBeanDefinition();
                beanDefinition.setClazz(beanClass);
                String beanName = beanClass.getAnnotation(Component.class).value();
                beanDefinition.setScope(ScopeEnum.singleton);
                if (beanClass.isAnnotationPresent(Scope.class)) {
                    Scope scope = (Scope) beanClass.getAnnotation(Scope.class);
                    if (ScopeEnum.proptype.name().equals(scope.value())) {
                        beanDefinition.setScope(ScopeEnum.proptype);
                    }
                }
                beanDefinitionMap.put(beanName,beanDefinition);
            }
        }
    }

    private List<Class<?>> getBeanClass(String path) throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        ClassLoader classLoader = KasonApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(path);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File file1 : files) {
                String classPath = file1.getPath();
                if (classPath.endsWith(".class")) {
                    classPath = classPath.substring(classPath.indexOf("com"),classPath.indexOf(".class")).replace("\\",".");
                    Class<?> aClass = classLoader.loadClass(classPath);
                    classes.add(aClass);
                }
            }
        }
        return classes;
    }


    public Object getBean(String beanName) throws Exception {
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        }
        return doCreateBean(beanName,beanDefinitionMap.get(beanName));
    }



}
